import os
import re
import xml.etree.cElementTree as ET

ns = {'xmlns': 'http://graphml.graphdrawing.org/xmlns',
      'y': 'http://www.yworks.com/xml/graphml'}

def load(infile):
    return ET.parse(infile)

def dump(cwd, tree, outfile, extdata = None):
    entityList = {}
    treeRoot = tree.getroot()
    for node in treeRoot.iterfind('.//xmlns:node', ns):
        entityId = node.attrib['id']
        entityParam, entityBody, rawText = parse_label(node.find('.//y:NodeLabel', ns))
        entityList[entityId] = {'type': 'node', 'id': entityId,
                                'children': [], 'precondition': None, 'behaviour': '',
                                'param': entityParam, 'body': entityBody, 'text': rawText}
    for edge in treeRoot.iterfind('.//xmlns:edge', ns):
        entityId = edge.attrib['id']
        entityParam, entityBody, rawText = parse_label(edge.find('.//y:EdgeLabel', ns))
        entityList[entityId] = {'type': 'edge', 'id': entityId,
                                'param': entityParam, 'body': entityBody, 'text': rawText}
        if 'source' in edge.attrib and 'target' in edge.attrib:
            sourceEntity = entityList[edge.attrib['source']]
            targetEntity = entityList[edge.attrib['target']]
            sourceEntity['children'].append(targetEntity)
            targetEntity['precondition'] = entityList[entityId]
    instructions = []
    with open(outfile, 'w') as fp:
        if extdata:
            write_whiteboard_data(fp, cwd, extdata)
        for entity in entityList.values():
            if entity['type'] == 'node' and not entity['children'] and not entity['precondition']:
                if not try_preprocess_instructions(entity['text'], instructions):
                    write_whiteboard_data(fp, cwd, entity['text'])
        for entity in entityList.values():
            if entity['type'] == 'node' and entity['children'] and not entity['precondition']:
                write_behaviour_tree(fp, entity, instructions)
                break

def try_preprocess_instructions(text, instructions):
    matchObj = re.match(r'\s*\[\s*initializer\s*\]\s*', text)
    if matchObj:
        for instruction in text[matchObj.end():].split('\n'):
            if instruction.strip():
                instructions.append(rewrite_instruction(instruction.strip()))
        return True

def write_behaviour_tree(fp, root, instructions):
    write_entry_source(fp, root, instructions)
    behaviourWaitingRoom = [root]
    while behaviourWaitingRoom:
        entity = behaviourWaitingRoom[0]
        precondition = entity['precondition']
        if precondition and precondition['body']:
            write_precondition_source(fp, precondition)
        parse_behaviour_type(entity)
        sort_behaviour_children(entity)
        write_behaviour_source(fp, entity)
        behaviourWaitingRoom[:1] = entity['children']

def write_whiteboard_data(fp, cwd, text):
    if text.lstrip().startswith('file://'):
        with open(os.path.join(cwd, text.strip()[7:]), 'r') as fpo:
            text = fpo.read()
    for matchobj in re.finditer(r'\s*(\w+)\s*=(.+?);', text, re.DOTALL):
        fp.write('local %s = %s\n' % (matchobj.group(1), matchobj.group(2).strip()))
    fp.write('----------------------------------------------------------------\n\n')

def parse_label(label):
    if label is None:
        return (), '', ''
    rawText = label.text
    tidyText = re.sub(r'\s+', ' ', rawText.strip())
    if not tidyText.startswith('['):
        return (), tidyText, rawText
    paramText, bodyText = tidyText[1:].split(']', 1)
    return re.split(r'\s?,\s?', paramText.strip()), bodyText.lstrip(), rawText

def rewrite_graphml_source(text):
    def replace_possible_method(matchobj):
        if matchobj.group(1) not in ('and', 'or', 'not'):
            return 'obj:' + matchobj.group(1) + '('
        return matchobj.group(1) + ' ('
    tidyText = re.sub(r'\s?\(\s?', ' (', re.sub(r'\s?\)\s?', ') ', text))
    tidyText = re.sub(r'\(\s?\(', '((', re.sub(r'\)\s?\)', '))', tidyText))
    tidyText = re.sub(r'\(\s?\(', '((', re.sub(r'\)\s?\)', '))', tidyText))
    tidyText = re.sub(r'\s?,\s?', ', ', tidyText.strip())
    return re.sub(r'(\w+)\s\(', replace_possible_method, tidyText)

def rewrite_behaviour(text):
    finalText = rewrite_graphml_source(text).replace('obj:', '', 1)
    nameText, paramText = finalText.split('(', 1)
    extraText = '(bb' + ('' if paramText == ')' else ', ')
    return nameText + extraText + paramText

def rewrite_instruction(text):
    finalText = rewrite_graphml_source(text).replace('obj:', '', 1)
    nameText, paramText = finalText.split('(', 1)
    extraText = '(obj, bb' + ('' if paramText == ')' else ', ')
    return nameText + extraText + paramText

def split_behaviour_name(text):
    return text.split('(', 1)[0].rstrip()

def parse_behaviour_type(entity):
    entity['behaviour'] = split_behaviour_name(entity['body'])

def sort_behaviour_children(entity):
    def key(child):
        precondition = child['precondition']
        if not precondition: return 0
        param = precondition['param']
        if not param: return 0
        return param
    if entity['behaviour'] in ('AIPrioritySelector',):
        entity['children'].sort(key=key)

def write_precondition_source(fp, entity):
    fp.write('function t.%s()\n' % (entity['id']))
    fp.write('\treturn %s\n' % (rewrite_graphml_source(entity['body'])))
    fp.write('end\n\n')

def write_behaviour_source(fp, entity):
    fp.write('function t.%s()\n' % (entity['id']))
    fp.write('\tlocal node = %s\n' % (rewrite_behaviour(entity['body'])))
    precondition = entity['precondition']
    if precondition and precondition['body']:
        fp.write('\tnode:SetExternalPrecondition(t.%s)\n' % (precondition['id']))
    for child in entity['children']:
        fp.write('\tnode:AppendChildNode(t.%s())\n' % (child['id']))
    fp.write('\treturn node\n')
    fp.write('end\n\n')

def write_entry_source(fp, entity, instructions):
    fp.write('local obj, bb, t = nil, nil, {}\n')
    fp.write('function BuildBehaviorTree(handler)\n')
    fp.write('\tobj, bb = handler, handler:GetBlackboard()\n')
    for instruction in instructions:
        fp.write('\t%s\n' % (instruction))
    fp.write('\tobj.Tree:SetRootNode(t.%s())\n' % (entity['id']))
    fp.write('end\n\n')
